/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "BackupHandler.h"

#include <map>
#include <thread>
#include <iostream>
#include <fstream>
#include <functional>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
#include "BackintConst.h"
#include "common/File.h"
#ifdef EBK_TRACE_POINT
#include "tracepoint/EbkTracePoint.h"
#endif

using namespace backint;

namespace {
    const std::string BACKUP_SUCCESS = "#SAVED";
    const std::string BACKUP_ERROR = "#ERROR";
    const std::string VERSION_1_0 = "1.0";
    const std::string CATA_LOG_IN_FILE = "log_backup_0_0_0_0"; // cata_log只有一个输入文件
    const int NO_DATA_SIZE = -1;
    const int READ_WRITE_DATA_BUFFER = 4096 * 1024;
    const int READ_TIMEOUT_SECONDS = 180; // 设置超时时间为 180 秒
    using Defer = std::shared_ptr<void>;
    const mp_string PARAM_PATH = "/usr/sap/";
    const mode_t PERMISSIONS = 0777;
}

BackupHandler::BackupHandler()
{
    m_backupFunMap.insert(std::make_pair<std::string, BackupFunc>("COMPLETE",
        std::bind(&BackupHandler::DataBackup, this, std::placeholders::_1, std::placeholders::_2)));
    m_backupFunMap.insert(std::make_pair<std::string, BackupFunc>("DIFFERENTIAL",
        std::bind(&BackupHandler::DataBackup, this, std::placeholders::_1, std::placeholders::_2)));
    m_backupFunMap.insert(std::make_pair<std::string, BackupFunc>("INCREMENTAL",
        std::bind(&BackupHandler::DataBackup, this, std::placeholders::_1, std::placeholders::_2)));
    m_backupFunMap.insert(std::make_pair<std::string, BackupFunc>("METADATA",
        std::bind(&BackupHandler::DataBackup, this, std::placeholders::_1, std::placeholders::_2)));
    m_backupFunMap.insert(std::make_pair<std::string, BackupFunc>("INITIALVOLUME",
        std::bind(&BackupHandler::DataBackup, this, std::placeholders::_1, std::placeholders::_2)));
    m_backupFunMap.insert(std::make_pair<std::string, BackupFunc>("CATALOG",
        std::bind(&BackupHandler::CataLogBackup, this, std::placeholders::_1, std::placeholders::_2)));
    m_backupFunMap.insert(std::make_pair<std::string, BackupFunc>("LOG",
        std::bind(&BackupHandler::LogBackup, this, std::placeholders::_1, std::placeholders::_2)));
    InitAndRegTracePoint();
}

uint32_t BackupHandler::ExecBackup(const StuParam &inParam, const PlugParams &plugParam)
{
    LOGGUARD("");
    GetBackupParam(inParam, plugParam);
    StuFileInfo stFileInfo;
    if (ReadPipeAndFile(inParam.m_inputFile, stFileInfo) != SUCCESS) {
        ERRLOG("Failed to read pipe and file, jobId=%s, subJobId=%s.", m_jobId.c_str(), m_subJobId.c_str());
        return FAILED;
    }
    INFOLOG("backup type %s.", inParam.m_backupType.c_str());
    auto iter = m_backupFunMap.find(inParam.m_backupType);
    if (iter != m_backupFunMap.end()) {
        return iter->second(inParam, stFileInfo);
    }
    ERRLOG("Undefined backup type %s.", inParam.m_backupType.c_str());
    return FAILED;
}

uint32_t BackupHandler::DataBackup(const StuParam &inParam, const StuFileInfo &stFileInfo)
{
    LOGGUARD("");
    std::vector<std::string> outFileTitle = {stFileInfo.m_inputFileTitle};
    if (WriteOutFile(outFileTitle, m_outputFile) != SUCCESS) {
        ERRLOG("Failed to write output file.");
        return FAILED;
    }
    ReadWriteData(inParam, stFileInfo);
    return BackupResult();
}

uint32_t BackupHandler::CataLogBackup(const StuParam &inParam, const StuFileInfo &stFileInfo)
{
    LOGGUARD("");
    std::vector<std::string> outFileTitle = {stFileInfo.m_inputFileTitle};
    if (WriteOutFile(outFileTitle, m_outputFile) != SUCCESS) {
        ERRLOG("Failed to write output file.");
        return FAILED;
    }
    CataLogReadWriteData(inParam, stFileInfo);
    return BackupResult();
}

uint32_t BackupHandler::LogBackup(const StuParam &inParam, const StuFileInfo &stFileInfo)
{
    LOGGUARD("");
    std::string vers;
    if (GetVersionFromInfile(inParam.m_inputFile, vers) != SUCCESS) {
        ERRLOG("Failed to get version.");
        return FAILED;
    }
    std::string fileName = GetFileName(stFileInfo.fileData[0].m_filePath);
    if (vers == VERSION_1_0 && stFileInfo.fileData.size() == 1) {
        if (fileName == CATA_LOG_IN_FILE) {
            return CataLogBackup(inParam, stFileInfo);
        } else if (fileName.find("incremental") != std::string::npos ||
        fileName.find("differential") != std::string::npos) {
            return DataBackup(inParam, stFileInfo);
        }
    }
    if (fileName.find("log") != std::string::npos) {
        INFOLOG("Start log backup.");
        std::vector<std::string> outFileTitle = {stFileInfo.m_inputFileTitle};
        INFOLOG("Start get title: %s.", stFileInfo.m_inputFileTitle.c_str());
        if (WriteOutFile(outFileTitle, m_outputFile) != SUCCESS) {
            ERRLOG("Failed to write output file.");
            return FAILED;
        }
        LogReadWriteData(inParam, stFileInfo);
        return BackupResult();
    }
    return SUCCESS;
}

uint32_t BackupHandler::ReadPipeAndFile(const std::string &inputFile, StuFileInfo &stFileInfo)
{
    if (ReadPipeInfo(inputFile, stFileInfo) != SUCCESS) {
        ERRLOG("Failed to read pipe info.");
        return FAILED;
    }
    if (stFileInfo.fileData.empty()) {
        ERRLOG("Pipe info and file info is empty.");
        return FAILED;
    }
    return SUCCESS;
}

void BackupHandler::GetBackupParam(const StuParam &inParam, const PlugParams &plugParam)
{
    m_pid = plugParam.m_pid;
    m_dbName = plugParam.m_dbName;
    m_jobId = plugParam.m_jobId;
    m_subJobId = plugParam.m_subJobId;
    m_cachePath = plugParam.m_cachePath;
    m_dataPath = plugParam.m_dataPath;
    m_outputFile = inParam.m_outFile;
    m_backupID = inParam.m_dtBackupId;
    m_resultFile = m_cachePath + "/" + "result_" + m_dbName + "_backup_" + m_pid + ".txt";
}

void BackupHandler::ReadWriteData(const StuParam &inParam, const StuFileInfo &fileInfo)
{
    LOGGUARD("");
    std::string parentPath = m_dataPath + "/" + inParam.m_dtBackupId;
    std::string dbName;
    std::string filePh = inParam.m_inputFile;
    if (GetDbNameFromInfile(filePh, dbName) != SUCCESS) {
        ERRLOG("Failed to get db name from file.");
        return;
    }
    for (const auto &file : fileInfo.fileData) {
        std::string outputFile = GetFileName(file.m_filePath);
        outputFile = m_dataPath + "/" + outputFile;
        std::thread readFileThreads(&BackupHandler::WriteDataToFile, this, file.m_filePath, outputFile,
            file.m_dataLength);
        readFileThreads.detach();
        std::lock_guard<std::mutex> lock(m_threadMutex);
        m_threadCount++;
    }
    while (true) {
        if (m_threadCount == 0) {
            INFOLOG("All read write data thread exit, will return.");
            return;
        }
    }
}

void BackupHandler::CataLogReadWriteData(const StuParam &inParam, const StuFileInfo &fileInfo)
{
    LOGGUARD("");
    // catalog: <data_repo>/<resouce_id>/<catalog_id>/log_backup_0_0_0_0
    std::string parentPath = GetCatalogParentPath(inParam, m_dataPath);
    if (parentPath.empty()) {
        return;
    }
    INFOLOG("parentPath:%s.", parentPath.c_str());
    if (CreateDirs(parentPath) != SUCCESS) {
        ERRLOG("Create backup id dir failed, parentPath=%s.", parentPath.c_str());
        return;
    }
    for (const auto &file : fileInfo.fileData) {
        std::string outputFile = GetFileName(file.m_filePath);
        outputFile = parentPath + "/" + outputFile;
        INFOLOG("outputFile:%s.", outputFile.c_str());
        std::thread readFileThreads(&BackupHandler::WriteDataToFile, this, file.m_filePath, outputFile,
            file.m_dataLength);
        readFileThreads.detach();
        std::lock_guard<std::mutex> lock(m_threadMutex);
        m_threadCount++;
    }
    while (true) {
        if (m_threadCount == 0) {
            INFOLOG("Cata log read write data thread exit, will return.");
            return;
        }
    }
}

void BackupHandler::LogReadWriteData(const StuParam &inParam, const StuFileInfo &fileInfo)
{
    LOGGUARD("");
    std::string dbName;
    std::string filePh = inParam.m_inputFile;
    if (GetDbNameFromInfile(filePh, dbName) != SUCCESS) {
        ERRLOG("Get db name failed.");
        return;
    }
    INFOLOG("Get log db name: %s.", dbName.c_str());
    std::string logParentPath;
    std::string parFile = GetExeCurrentPath() + "/hdbconfig/backint.conf";
    if (GetLogParentPath(parFile, logParentPath) != SUCCESS) {
        ERRLOG("Failed to get log path.");
        return;
    }
    // 数据库日志目录：界面配置的logBackupPath/dbName
    std::string logTargetDir = logParentPath + "/" + dbName;
    std::string outputParentDir = logTargetDir + "/" + inParam.m_dtBackupId;
    INFOLOG("Get log target Dir: %s.", outputParentDir.c_str());
    if (CreateDirs(outputParentDir) != SUCCESS) {
        ERRLOG("Create backup id dir failed:%s.", outputParentDir.c_str());
        return;
    }
    for (const auto &file : fileInfo.fileData) {
        std::string outputFile = GetFileName(file.m_filePath);
        outputFile = outputParentDir + "/" + outputFile;
        INFOLOG("Log file path:%s.", outputFile.c_str());

        std::thread readFileThreads(&BackupHandler::WriteDataToFile, this, file.m_filePath, outputFile,
            file.m_dataLength);
        readFileThreads.detach();
        std::lock_guard<std::mutex> lock(m_threadMutex);
        m_threadCount++;
    }
    while (true) {
        if (m_threadCount == 0) {
            INFOLOG("Log read write data thread exit, will return.");
            return;
        }
    }
}

uint32_t BackupHandler::BackupResult()
{
    m_outputValue["pid"] = m_pid;
    m_outputValue["job_id"] = m_jobId;
    m_outputValue["sub_jod_id"] = m_subJobId;
    if (m_backupResult) {
        m_outputValue["code"] = SUCCESS;
        WriteResultToFile(m_outputValue, m_resultFile);
        return SUCCESS;
    } else {
        m_outputValue["code"] = FAILED;
        WriteResultToFile(m_outputValue, m_resultFile);
        return FAILED;
    }
}

uint32_t BackupHandler::WriteDataToFile(const std::string &inputFile, const std::string &outputFile, int dataSize)
{
    LOGGUARD("");
    Defer _(nullptr, [&](...) {
        std::lock_guard<std::mutex> lock(m_writeOutFileMutex);
        WriteOutFile(m_outputResult, m_outputFile);
    });
    int inFile = open(inputFile.c_str(), O_RDONLY);
    int outFile = open(outputFile.c_str(), O_WRONLY| O_APPEND |O_CREAT, PERMISSIONS);
    if (inFile < 0 || outFile < 0) {
        std::lock_guard<std::mutex> lock(m_threadMutex);
        m_backupResult = false;
        ERRLOG("Failed to open input or output file: %s, %s,.", inputFile.c_str(), outputFile.c_str());
    } else {
        unsigned long long size = 0;
        char readBuffer[READ_WRITE_DATA_BUFFER + 1] = {0};
        std::size_t tmpLens = 0;

        while (1) {
            tmpLens = read(inFile, readBuffer, READ_WRITE_DATA_BUFFER);
            size += tmpLens;
            if (tmpLens == 0) {
                break;
            }
            write(outFile, readBuffer, tmpLens);
        }
        if (dataSize != NO_DATA_SIZE) {
            size = dataSize;
        }
        m_outputResult = {BACKUP_SUCCESS, m_backupID, inputFile, std::to_string(size)};
    }

    if (0 < inFile) {close(inFile);}
    if (0 < outFile) {close(outFile);}
    std::lock_guard<std::mutex> lock(m_threadMutex);
    m_threadCount--;
    if (!m_backupResult) {
        m_outputResult = {BACKUP_ERROR, inputFile};
        return FAILED;
    }
    return SUCCESS;
}

uint32_t BackupHandler::SelectFile(int inFile)
{
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(inFile, &fds);
    struct timeval timeout;
    timeout.tv_sec = READ_TIMEOUT_SECONDS;
    timeout.tv_usec = 0;
    int ret = select(inFile + 1, &fds, NULL, NULL, &timeout);
    return ret;
}

#ifdef EBK_TRACE_POINT
void CallBackupCrush(std::string name, size_t paramNum, ...)
{
    std::raise(SIGKILL);
}
#endif

void BackupHandler::InitAndRegTracePoint()
{
#ifdef EBK_TRACE_POINT
    // 注册TP点
    INFOLOG("Register tp point");
    (void)EbkTracePoint::GetInstance().RegTP("saphana_backint_crush", "crush", TP_TYPE_CALLBACK, CallBackupCrush);
    int aliveNum = 3;
    (void)EbkTracePoint::GetInstance().ActiveTPImpl("saphana_backint_crush", aliveNum, "saphana_backint_crush", 0);
#endif
}